5.13. Основные принципы обработки ошибок в Rust
Основные принципы обработки ошибок в Rust
Основные принципы обработки ошибок в Rust
-
Ошибки — это значения.
Функции, которые могут завершиться неудачей, возвращают типResult<T, E>, где:T— тип успешного результата,E— тип ошибки.
-
Нет
try/catch,throw,raise.
Обработка ошибок происходит явно через сопоставление с образцом (match), макросы (?), или комбинаторы (map,and_thenи т.д.). -
Паника (
panic!) существует, но предназначена только для необратимых ошибок (например, нарушение инвариантов, ошибки в логике программы).- Вызывает разворачивание стека (unwinding) или немедленное завершение (
abort). - Не должна использоваться для обработки ожидаемых ошибок (например, отсутствие файла, недопустимый ввод).
- Вызывает разворачивание стека (unwinding) или немедленное завершение (
Типы ошибок в Rust
Rust не определяет глобальную иерархию ошибок. Вместо этого используются конкретные типы, реализующие трейт std::error::Error.
1. Стандартные типы ошибок из std
-
std::io::Error— ошибки ввода-вывода:- Возникают при работе с файлами, сетью, процессами.
- Содержит код ошибки (
std::io::ErrorKind), например:NotFoundPermissionDeniedConnectionRefusedInvalidInputTimedOut- и др.
-
std::fmt::Error— ошибка при форматировании (например, в пользовательскомDisplay::fmt). -
std::num::ParseIntError,std::num::ParseFloatError— ошибки при парсинге чисел. -
std::str::Utf8Error— ошибка при проверке UTF-8. -
std::string::FromUtf8Error— ошибка при созданииStringиз байтов. -
std::env::VarError— ошибка при чтении переменной окружения:NotPresentNotUnicode
-
std::ffi::FromBytesWithNulError,std::ffi::NulError— ошибки при работе с C-совместимыми строками. -
std::path::StripPrefixError— ошибка при удалении префикса пути.
2. Трейт std::error::Error
Любой пользовательский тип ошибки должен реализовывать этот трейт (обычно автоматически через thiserror или вручную). Он требует:
Display::fmt— человекочитаемое сообщение,Debug::fmt— техническое представление,- опционально:
source()— ссылка на вложенную ошибку (цепочка причин).
3. Паники (не ошибки в обычном смысле)
Следующие ситуации вызывают panic!:
- Выход за границы массива:
index out of bounds - Деление на ноль
- Нарушение
assert!илиunwrap()наErr/None - Некорректная работа с
unsafe(например, двойное освобождение памяти)
Эти события не являются типами ошибок, а приводят к аварийному завершению потока.
Экосистемные практики
- Библиотеки определяют собственные типы ошибок, часто с помощью крейта
thiserror(для удобного определения) илиanyhow(для упрощённой обработки в приложениях). - Цепочки ошибок строятся через поле
source, а не через наследование. - Нет общего корневого типа ошибок, кроме как через динамическую диспетчеризацию (
Box<dyn std::error::Error>).
Примеры
use std::fs;
use std::num::ParseIntError;
fn read_number_from_file(path: &str) -> Result<i32, Box<dyn std::error::Error>> {
let contents = fs::read_to_string(path)?; // io::Error
let number: i32 = contents.trim().parse()?; // ParseIntError
Ok(number)
}
Здесь возможны две конкретные ошибки: std::io::Error и ParseIntError. Обе реализуют std::error::Error и могут быть упакованы в Box<dyn Error>.
Нет типов ошибок
В Rust нет единого списка «типов ошибок», аналогичного другим языкам, потому что:
- Ошибки представлены конкретными типами, а не иерархией классов.
- Нет встроенных исключений вроде
IndexError,KeyError. - Стандартная библиотека предоставляет набор специализированных типов ошибок для разных подсистем.
- Пользовательские ошибки определяются явно и композиционно.
Таким образом, вместо таксономии исключений Rust предлагает типобезопасную, композиционную модель обработки ошибок через Result и трейт Error.